#define AUDIO_ENABLE_AAC
#define AUDIO_BUFFER_SIZE 16384

#include "Arduino.h"
#include "WiFi.h"
#include "Audio.h"
#include "WebServer.h"
#include "TFT_eSPI.h"  // 彩屏支持
#include "web_server.h"  // 引入配网模块
#include "GUI.H" //引入字库
#include "Preferences.h"
//LittleFS
#include "FS.h"
#include "LittleFS.h"

extern Preferences preferences; //声明存储
// ==================== I2S 引脚定义 ====================
#define I2S_DOUT      7
#define I2S_BCLK      15
#define I2S_LRC       16

// ==================== 屏幕背光引脚 ====================
#define TFT_BACKLIGHT_PIN 42  // 背光控制引脚

// =============== 背光 PWM 控制（调暗）===============
#define PWM_CHANNEL      0
#define PWM_FREQ         5000
#define PWM_RESOLUTION   8  // 8位 → 0~255

// ============ 商业级 UI 配色方案 ============
#define BG_COLOR        TFT_BLACK       // 背景
#define PRIMARY_COLOR   0x00BFFF        // 亮青色（主色调）
#define SECONDARY_COLOR 0xFFA500        // 橙色（强调色）
#define TEXT_LIGHT      TFT_WHITE       // 主文字（白色）
#define TEXT_DIM        0xAAAAAA        // 次要文字（浅灰）
#define ACCENT_COLOR    TFT_YELLOW      // 高亮色（如天气）
#define PROGRESS_BG     0x333333        // 进度条背景灰
#define BORDER_COLOR    0x444444        // 分隔线颜色

// ==================== WiFi 信息 ====================
//const char* ssid = "3-8-6";
//const char* password = "115889198";

// ==================== 当前播放 URL ====================
// 默认播放重庆音乐FM
char current_url[256] = "https://lhttp.qtfm.cn/live/647/64k.mp3";
// 默认播放源
char json_url[256] = "https://fm-73y.pages.dev/list.json"; // 默认电台列表地址
// ==================== 自定义 HTTP 请求头 ====================
const char* customHeaders = 
    "Referer: https://ting15.com/kongbulingyi/2047/0-1.html\r\n"
    "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36\r\n"
    "X-Custom-Header: ArduinoAudioPlayer\r\n";

// ==================== 彩屏对象 ====================
TFT_eSPI tft = TFT_eSPI();  // 320x240 屏幕

// ==================== 全局变量 ====================
Audio audio;
WebServer server(80);
bool isPlaying = false;
unsigned long connectStart = 0;         // 开始播放的时间
unsigned long lastScreenUpdate = 0;     // 最后屏幕刷新时间
int volumeLevel = 10;                   // 声音音量 (0~21)
bool screenOn = true;                   // 屏幕是否开启
int lastBrightness = 30;                // 记录最后一次非零亮度（用于“开屏”恢复）
char g_ssid[64] = "";                   // ✅ 用来保存 WiFi 名称
// 🔽 新增：定义按钮引脚（ESP32 开发板上的 BOOT 按钮）
const int BUTTON_PIN = 0;  // 大多数 ESP32 板子的 BOOT 按钮接 GPIO0
//播放地址键值对
const char* PREF_URL_NAMESPACE = "audio_player";
//最后一次播放地址
const char* PREF_URL_KEY_URL = "last_url";
// 播放源JSON地址
const char* PREF_JSON_NAMESPACE = "json_source";     // 命名空间
const char* PREF_JSON_KEY_URL = "json_url";          // 键名

//开机声音设置
const char* PREF_VOICE_NAMESPACE = "power_VOICE_val";     // 命名空间
const char* PREF_VOICE_KEYL = "power_VOICE_key";          // 键名

const char* PREF_BRIGHTNESS_NAMESPACE = "brightness";     // 命名空间
const char* PREF_BRIGHTNESS_KEYL = "brightness_key";          // 键名

// 全局变量：用于跟踪关机倒计时
unsigned long shutdownStartTime = 0;  // 倒计时开始的时间（millis）
int shutdownDelayMinutes = 0;         // 延迟多少分钟
bool shutdownTimerActive = false;     // 是否正在倒计时


int lastReportedMinute = -1;  // 全局变量，记录最后一次打印关机的是第几分钟
bool wasStopped = false; // 修复作用域错误

// ==================== 全局变量 ====================


// ==================== Audio 回调函数 ====================
void my_audio_info(Audio::_msg msg) {
    if (msg.s) {
        //Serial.print("📢 Audio回调信息：");
        //Serial.println(msg.s);

        if (strcmp(msg.s, "eof") == 0 ||
            strcmp(msg.s, "Audio stopped") == 0 ||
            strcmp(msg.s, "Stopped") == 0 ||
            strstr(msg.s, "Connection closed") ||
            strstr(msg.s, "End of stream") ||
            strstr(msg.s, "HTTP error") ||
            strstr(msg.s, "Connect failed")) {
            isPlaying = false;
            Serial.println("⏹️ 播放结束");
        }

        if (strstr(msg.s, "Data start") || strstr(msg.s, "Playing")) {
            isPlaying = true;
            connectStart = millis();
        }
    }
}

// ==================== 在屏幕上显示信息 ====================
void updateScreen() {
    if (!screenOn) return;

    static bool initialized = false;
    static String lastTime, lastDate, lastWeather, lastTitle;
    static int lastVol = -1, lastProgress = -1;
    static unsigned long lastPlayTime = 0;
    static bool wasStopped = false;

    const int W = tft.width(), H = tft.height();

    if (!initialized) {
        initUI(); // 初始化静态UI（只执行一次）
        initialized = true;
    }

    // === 动态刷新各模块 ===
    updateTopBar();      // 时钟、日期、天气
    updateNowPlaying();  // 歌曲名（可滚动）
    updateProgress();    // 进度条
    updatePlayTime();    // 播放时间（仅刷新数字）
    updateBottomBar();   // 音量、倒计时、网络
}

void initUI() {
    tft.fillScreen(BG_COLOR);

    // --- 顶部状态栏背景 ---
    tft.fillRect(0, 0, 320, 50, 0x111111); // 深灰背景

    // --- 标题 "Now Playing" ---
    tft.setTextSize(2);
    tft.setTextColor(PRIMARY_COLOR);
    tft.setTextDatum(MC_DATUM);
    tft.drawString("🎵 Now Playing", 160, 80);

    // --- 静态 "⏱️" 图标 + 冒号 ---
    tft.setTextSize(2);
    tft.setTextColor(TEXT_DIM);
    tft.setTextDatum(TL_DATUM);
    tft.drawString("⏱️", 40, 180); // 图标
    tft.drawString(":", 130, 180); // 冒号

    // --- 静态音量图标 ---
    tft.drawString("🔊", 20, 220);

    // --- 分隔线 ---
    tft.drawLine(0, 50, 320, 50, BORDER_COLOR);
    tft.drawLine(0, 150, 320, 150, BORDER_COLOR);
}
void updateTopBar() {
    static String lastTimeStr = "", lastDateStr = "", lastWeatherStr = "";

    String timeStr = getTimeString(); // "18:05:23"
    String dateStr = getDateString(); // "2025-09-15 周一"
    String weatherStr = getWeatherString(); // "☀️ 28°C 晴"

    if (timeStr != lastTimeStr) {
        tft.setTextSize(2);
        tft.setTextColor(TEXT_LIGHT);
        tft.fillRect(10, 5, 150, 20, 0x111111);
        tft.setCursor(10, 20);
        tft.print(timeStr);
        lastTimeStr = timeStr;
    }

    if (dateStr != lastDateStr) {
        tft.setTextSize(1);
        tft.setTextColor(TEXT_DIM);
        tft.fillRect(10, 25, 200, 15, 0x111111);
        tft.setCursor(10, 35);
        tft.print(dateStr);
        lastDateStr = dateStr;
    }

    if (weatherStr != lastWeatherStr) {
        tft.setTextSize(1);
        tft.setTextColor(ACCENT_COLOR);
        tft.setTextDatum(TR_DATUM);
        tft.fillRect(170, 5, 140, 30, 0x111111);
        tft.drawString(weatherStr, 310, 25);
        lastWeatherStr = weatherStr;
    }
}
void updateBottomBar() {
    static int lastVol = -1;
    static int lastCountdown = -1;

    // 音量
    if (volumeLevel != lastVol) {
        tft.fillRect(50, 215, 50, 20, BG_COLOR);
        tft.setTextSize(1);
        tft.setTextColor(TEXT_LIGHT);
        tft.setCursor(50, 220);
        tft.printf("%d", volumeLevel);
        lastVol = volumeLevel;
    }

    // 关机倒计时
    int cd = getShutdownCountdown();
    if (cd != lastCountdown) {
        tft.fillRect(120, 215, 80, 20, BG_COLOR);
        if (cd > 0) {
            int m = cd / 60, s = cd % 60;
            tft.setCursor(120, 220);
            tft.printf("⏱️%02d:%02d", m, s);
        }
        lastCountdown = cd;
    }

    // SSID（每30秒刷新一次，避免频繁）
    static unsigned long lastSsidUpdate = 0;
    if (millis() - lastSsidUpdate > 30000) {
        tft.fillRect(220, 215, 90, 20, BG_COLOR);
        tft.setCursor(220, 220);
        tft.printf("📶%s", WiFi.SSID().substring(0,6).c_str());
        lastSsidUpdate = millis();
    }
}
void updateProgress() {
    static int lastProgress = -1;
    
    if (!isPlaying) return;

    // 假设有 getTotalTime() 和 getCurrentTime()
    int total = getTotalTime(); // 总时长秒数
    int current = (millis() - connectStart) / 1000;
    int progress = total > 0 ? (current * 100) / total : 0;

    if (progress != lastProgress) {
        int barX = 40, barY = 155, barW = 240, barH = 8;
        
        // 清除进度条
        tft.fillRect(barX, barY, barW, barH, PROGRESS_BG);
        
        // 绘制已播放部分
        int fillW = (barW * progress) / 100;
        if (fillW > 0) {
            tft.fillRect(barX, barY, fillW, barH, PRIMARY_COLOR);
        }

        lastProgress = progress;
    }
}
int getTotalTime(){
    return 180;
}
void updatePlayTime() {
    static int lastMin = -1, lastSec = -1;

    if (!isPlaying) {
        if (!wasStopped) { // ✅ 现在 wasStopped 是全局变量，可访问
            tft.fillRect(70, 175, 100, 20, BG_COLOR);
            tft.setTextSize(2);
            tft.setTextColor(TFT_RED);
            tft.setTextDatum(MC_DATUM);
            tft.drawString("⏹️ STOPPED", 160, 180);
            wasStopped = true;
        }
        return;
    }

    if (wasStopped) { // ✅ 同上
        tft.fillRect(70, 175, 100, 20, BG_COLOR);
        tft.setTextSize(2);
        tft.setTextColor(TEXT_DIM);
        tft.setTextDatum(TL_DATUM);
        tft.drawString(":", 130, 180);
        wasStopped = false;
        lastMin = lastSec = -1;
    }

    unsigned long playTime = (millis() - connectStart) / 1000;
    int min = playTime / 60, sec = playTime % 60;

    if (min != lastMin || sec != lastSec) {
        tft.fillRect(70, 175, 55, 20, BG_COLOR);
        tft.fillRect(135, 175, 30, 20, BG_COLOR);

        tft.setTextSize(2);
        tft.setTextColor(TEXT_LIGHT);
        tft.setCursor(70, 180);
        tft.printf("%d", min);
        tft.setCursor(135, 180);
        tft.printf("%02d", sec);

        lastMin = min; lastSec = sec;
    }
}
String updateNowPlaying(){
    return "Beyond-光辉岁月";
}
// 需要实现的辅助函数（示例）
String getTimeString() {
    time_t now = time(nullptr);
    struct tm *timeinfo = localtime(&now);
    char buffer[9];
    strftime(buffer, sizeof(buffer), "%H:%M:%S", timeinfo);
    return String(buffer);
}

String getDateString() {
    time_t now = time(nullptr);
    struct tm *timeinfo = localtime(&now);
    char buffer[20];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %a", timeinfo); // %a = 星期缩写
    return String(buffer);
}

String getWeatherString() {
    // 这里应该从天气API获取实际天气
    // 示例返回天气信息
    return "Sunny 25°C";
}

int getShutdownCountdown() {
    // 这里应该获取实际的关机倒计时
    // 示例返回300秒（5分钟）
    return 300;
}
// ==================== 播放指定 URL ====================
void playUrl(const char* url) {
    audio.stopSong();
    // 等待音频完全停止
    while (audio.isRunning()) {
        audio.loop();
        delay(1);
    }

    strcpy(current_url, url);
    Serial.print(F("▶️ 开始播放: "));
    Serial.println(url);
    isPlaying = true;
    connectStart = millis();
    audio.connecttohost2(url, "", "", customHeaders);
}
// ==================== 处理停止播放 ====================
void handleStop() {
    audio.stopSong();
    isPlaying = false;
    Serial.println("⏹️ 用户请求停止播放");
    server.send(200, "text/plain", "OK: Playback stopped");
}

// ==================== 设置音量 ====================
void handleSetVolume() {
    if (server.hasArg("vol")) {
        int newVol = server.arg("vol").toInt();
        if (newVol >= 0 && newVol <= 21) {
            volumeLevel = newVol;
            audio.setVolume(volumeLevel);
            server.send(200, "text/plain", "OK: Volume set to " + String(volumeLevel));
            updateScreen();
        } else {
            server.send(400, "text/plain", "Volume out of range (0-21)");
        }
    } else {
        server.send(400, "text/plain", "No volume provided");
    }
}

// ==================== 设置屏幕亮度 (0~100%) ====================
void handleSetBrightness() {
    if (server.hasArg("brightness")) {
        int bright = server.arg("brightness").toInt();
        if (bright >= 0 && bright <= 100) {
            int pwmValue = map(bright, 0, 100, 0, 255);
            ledcWrite(TFT_BACKLIGHT_PIN, pwmValue);
            if (bright > 0) {
                lastBrightness = bright;  // 仅非零时保存
                screenOn = true;
            } else {
                screenOn = false;
            }
            server.send(200, "text/plain", "OK: Brightness set to " + String(bright) + "%");
        } else {
            server.send(400, "text/plain", "Invalid brightness. Use 0-100");
        }
    } else {
        server.send(400, "text/plain", "No brightness provided");
    }
}

// ==================== 控制屏幕开关（兼容亮度）====================
void handleScreenControl() {
    String action = server.arg("action");  // on 或 off

    if (action == "off") {
        ledcWrite(TFT_BACKLIGHT_PIN, 0);
        screenOn = false;
        server.send(200, "text/plain", "Screen OFF");
    } else if (action == "on") {
        int pwmValue = map(lastBrightness, 0, 100, 0, 255);
        ledcWrite(TFT_BACKLIGHT_PIN, pwmValue);
        screenOn = true;
        updateScreen();
        server.send(200, "text/plain", "Screen ON (Brightness: " + String(lastBrightness) + "%)");
    } else {
        server.send(400, "text/plain", "Invalid action. Use 'on' or 'off'");
    }
}

// ==================== 更换播放链接 ====================
void handlePlay() {
    if (server.hasArg("url")) {
        String newUrl = server.arg("url");
        if (newUrl.length() < 256) {
            strcpy(current_url, newUrl.c_str());
            //存储URL键值对
              // ✅ 保存到 Preferences
            preferences.begin(PREF_URL_NAMESPACE , false); // 打开命名空间
            preferences.putString(PREF_URL_KEY_URL, current_url); // 用 key 保存
            preferences.end(); // 记得关闭
            playUrl(current_url);
            server.send(200, "text/plain", "OK: 播放已切换\n" + newUrl);
        } else {
            server.send(400, "text/plain", "URL too long");
        }
    } else {
        server.send(400, "text/plain", "No URL provided");
    }
}
//保存播放源
void handleSaveJsonUrl(){
    if (server.hasArg("url")) {
        String newUrl = server.arg("url");
        if (newUrl.length() < 256) {
            strcpy(json_url, newUrl.c_str());
            // 保存到 Preferences
            preferences.begin(PREF_JSON_NAMESPACE, false);
            preferences.putString(PREF_JSON_KEY_URL, json_url);
            preferences.end();
            server.send(200, "text/plain", "OK: JSON URL 已保存\n" + newUrl);
        } else {
            server.send(400, "text/plain", "URL too long");
        }
    } else {
        server.send(400, "text/plain", "No URL provided");
    }
}

//保存开机声音
void handleSavePowerVoice(){
    if (server.hasArg("vol")) {
        int newVol = server.arg("vol").toInt();
        if (newVol >= 0 && newVol <= 21) {
            volumeLevel = newVol;
            audio.setVolume(volumeLevel);
              // 保存到 Preferences
            preferences.begin(PREF_VOICE_NAMESPACE, false);
            preferences.putInt(PREF_VOICE_KEYL, volumeLevel);
            preferences.end();
            server.send(200, "text/plain", "OK: Volume set to " + String(volumeLevel));
        } else {
            server.send(400, "text/plain", "Volume out of range (0-21)");
        }
    } else {
        server.send(400, "text/plain", "No volume provided");
    }
}
//保存开机亮度
void handleSavePowerBrightness(){
    if (server.hasArg("brightness")) {
        int newBrightness = server.arg("brightness").toInt();
        if (newBrightness >= 0 && newBrightness <= 100) {
            
            lastBrightness = newBrightness;
            int pwmValue = map(lastBrightness, 0, 100, 0, 255);
            ledcWrite(TFT_BACKLIGHT_PIN, pwmValue);
              // 保存到 Preferences
            Serial.print("保存开机亮度Preferences");
            Serial.print(newBrightness);
            preferences.begin(PREF_BRIGHTNESS_NAMESPACE, false);
            preferences.putInt(PREF_BRIGHTNESS_KEYL, lastBrightness);
            preferences.end();
            server.send(200, "text/plain", "OK: Brightness set to " + String(newBrightness));
        } else {
            server.send(400, "text/plain", "Brightness out of range (0-100)");
        }
    } else {
        server.send(400, "text/plain", "No Brightness provided");
    }
}

//设置关机Action
void handleShutdown() {
  if (server.hasArg("minutes")) {
    int minutes = server.arg("minutes").toInt();
    startShutdownTimer(minutes);
    server.send(200, "text/plain", "已设置 " + String(minutes) + " 分钟后关机");
  } else {
    server.send(400, "text/plain", "缺少参数：minutes");
  }
}

//取消关机Action
void handleCancel() {
  cancelShutdown();
  server.send(200, "text/plain", "关机倒计时已取消");
}

// ==================== 主页 HTML ====================
void handleRoot() {
    if (!LittleFS.exists("/index.html")) {
        server.send(404, "text/plain", "File Not Found");
        return;
    }

    File file = LittleFS.open("/index.html", "r");
    if (!file) {
        server.send(500, "text/plain", "Failed to open file");
        return;
    }

    String html = file.readString();
    file.close();

    // 替换模板变量
    html.replace("{status}", isPlaying ? "<i class='fas fa-circle-play'></i><span>正在播放</span>" : "<i class='fas fa-stop-circle'></i><span>已停止</span>");
    html.replace("{current_url}", String(current_url));
    html.replace("{volume}", String(volumeLevel));
    html.replace("{lastBrightness}", String(lastBrightness));

    server.send(200, "text/html", html);
}

//打开配置html页面
void handleConfig(){
     if (!LittleFS.exists("/config.html")) {
        server.send(404, "text/plain", "File Not Found");
        return;
    }

    File file = LittleFS.open("/config.html", "r");
    if (!file) {
        server.send(500, "text/plain", "Failed to open file");
        return;
    }
    String html = file.readString();
    file.close();
    server.send(200, "text/html", html);
}
// 🔽 检测是否长按按钮（3秒）触发清除 WiFi
// ==================== 检测长按按钮（非阻塞）====================
void checkFactoryReset() {
    static unsigned long pressStart = 0;
    static bool wasPressed = false;
    const int BUTTON_PIN = 0;

    bool isPressed = (digitalRead(BUTTON_PIN) == LOW);

    // 按下瞬间
    if (isPressed && !wasPressed) {
        pressStart = millis();
        Serial.println("⏳ 检测到按钮按下，长按3秒恢复出厂设置...");
    }
    // 松开瞬间
    else if (!isPressed && wasPressed) {
        if (millis() - pressStart >= 3000) {
            Serial.println("💥 长按3秒，正在清除 WiFi 配置...");
            clearSavedWiFi();
            Serial.println("✅ 清除完成，设备将重启！");
            delay(1000);
            ESP.restart();
        } else {
            Serial.println("🛑 按钮松开太早，取消恢复出厂设置。");
        }
        pressStart = 0;
    }
    wasPressed = isPressed;
}


void showAPInstructions() {
    tft.fillScreen(TFT_BLACK);
    tft.setTextSize(2);
    tft.setTextColor(TFT_WHITE);
    
    int xPos = 10;
    int yPos = 10;

    tft.setCursor(xPos, yPos);
    tft.println("未配置WiFi");
    yPos += 30;

    tft.setCursor(xPos, yPos);
    tft.println("正在启动配网...");
    yPos += 30;

    tft.setTextSize(1);
    Serial.println("❌ 未配置WiFi，启动配网模式...");

    tft.fillRect(0, yPos, tft.width(), tft.height() - yPos, TFT_BLACK);
    
    tft.setTextSize(2);
    tft.setTextColor(TFT_CYAN);
    tft.setCursor(xPos, yPos);
    tft.println("请用手机连接:");
    yPos += 35;

    tft.setTextSize(3);
    tft.setTextColor(TFT_YELLOW);
    tft.setCursor(xPos, yPos);
    tft.println(String(AP_SSID));
    yPos += 40;

    tft.setTextSize(2);
    tft.setTextColor(TFT_GREEN);
    tft.setCursor(xPos, yPos);
    tft.printf("密码: %s\n", AP_PASS);
    yPos += 35;

    tft.setTextSize(1);
    tft.setTextColor(TFT_WHITE);
    tft.setCursor(xPos, yPos);
    tft.println("然后访问:");
    yPos += 25;

    tft.setTextColor(TFT_BLUE);
    tft.setCursor(xPos, yPos);
    tft.println("http://192.168.4.1");
    yPos += 25;

    tft.setTextColor(TFT_LIGHTGREY);
    tft.setCursor(xPos, yPos);
    tft.println("连上后关闭键盘");
    yPos += 25;
    tft.setCursor(xPos, yPos);
    tft.println("点击[连接]按钮");
}


void listFiles() {
  Serial.println("\n📁 文件列表：");
  File root = LittleFS.open("/");
  if (!root) {
    Serial.println("❌ 无法打开根目录！");
    return;
  }

  File file = root.openNextFile();
  bool hasFiles = false;

  while (file) {
    hasFiles = true;
    String fileName = file.name();
    size_t fileSize = file.size();

    Serial.printf("📄 %s\t(%d bytes)\t", fileName.c_str(), fileSize);

    // 检查文件是否存在（双重验证）
    if (LittleFS.exists(fileName)) {
      Serial.println("✅ 存在");
    } else {
      Serial.println("❌ 不存在？异常！");
    }

    file = root.openNextFile();
  }

  if (!hasFiles) {
    Serial.println("📭 文件系统为空或未正确烧录。");
  }

  root.close();
}


/**
 * @brief 启动关机倒计时（非阻塞）
 * @param minutes 延迟多少分钟后关机
 */
void startShutdownTimer(int minutes) {
    if (minutes <= 0) return;

    shutdownDelayMinutes = minutes;
    shutdownStartTime = millis();
    shutdownTimerActive = true;

    Serial.printf("【关机】%d 分钟后进入深度睡眠（非阻塞）...\n", minutes);
}

/**
 * @brief 在 loop() 中不断调用，检查是否到了关机时间
 */
void handleShutdownNonBlocking() {
    if (!shutdownTimerActive) return;

    unsigned long elapsed = millis() - shutdownStartTime;
    unsigned long totalDelayMs = shutdownDelayMinutes * 60UL * 1000;
    int currentMinute = elapsed / 60000;

    // 只在每分钟切换时打印一次
    if (currentMinute != lastReportedMinute && currentMinute < shutdownDelayMinutes) {
        int remaining = shutdownDelayMinutes - currentMinute;
        Serial.printf("【倒计时】剩余: %d 分钟\n", remaining);
        lastReportedMinute = currentMinute;
    }

    // 到时间了，关机！
    if (elapsed >= totalDelayMs) {
        Serial.println("【关机】进入永久深度睡眠...");
        esp_sleep_enable_timer_wakeup(100LL * 365 * 24 * 3600 * 1000000); // 100年
        esp_deep_sleep_start();
    }
}
// ============ 取消关机 ============
void cancelShutdown() {
    if (shutdownTimerActive) {
        shutdownTimerActive = false;
        Serial.println("【取消】关机倒计时已取消");
    }
}

// ==================== Setup ====================
void setup() {
    // ========== 1. 基础硬件初始化（只做一次）==========
    Serial.begin(115200);
    delay(100);

    // 在 setup() 中初始化 LittleFS
    if (!LittleFS.begin(true)) {  // true 表示如果失败则格式化
        Serial.println("❌ 无法挂载 LittleFS 或格式化失败！");
        return;
    }
    Serial.println("✅ LittleFS 已挂载");

    listFiles(); // 调用我们写的函数


    // 初始化按钮引脚
    pinMode(BUTTON_PIN, INPUT_PULLUP);  // 使用内部上拉，按钮按下为 LOW
    tft.init();
    
    // 🔹【优化】先加载字体以便显示中文提示
    tft.loadFont(yh_14);
    tft.setRotation(1);
    tft.fillScreen(TFT_BLACK);
    tft.setTextColor(TFT_WHITE);
    tft.setTextSize(2);
    tft.setCursor(0, 0);
    tft.println("启动中...");

    // 背光初始化
    bool success = ledcAttachChannel(
        TFT_BACKLIGHT_PIN,
        PWM_FREQ,
        PWM_RESOLUTION,
        PWM_CHANNEL
    );

    if (!success) {
        Serial.println("❌ PWM 背光初始化失败！");
    } else {
        Serial.println("✅ PWM 背光已启用");
    }


    // setup() 中初始化亮度的代码（替换原错误代码）
    // 在 setup() 中读取亮度的部分
    preferences.begin(PREF_BRIGHTNESS_NAMESPACE, false);
    int initBrightness = preferences.getInt(PREF_BRIGHTNESS_KEYL, -1);
    preferences.end();

    if (initBrightness >= 0 && initBrightness <= 100) {
        Serial.print("✨读取到存储的亮度：");
        Serial.println(initBrightness);
        
        lastBrightness = initBrightness;  // ✅ 必须赋值！
        int pwmValue = map(lastBrightness, 0, 100, 0, 255);
        ledcWrite(TFT_BACKLIGHT_PIN, pwmValue);
        Serial.printf("✅ 启动亮度已设为"+ lastBrightness);
    } else {
        Serial.println("✨未读取到存储亮度，使用默认值 10%");
        lastBrightness = 10;  // ✅ 同样要赋值
        int pwmValue = map(lastBrightness, 0, 100, 0, 255);
        ledcWrite(TFT_BACKLIGHT_PIN, pwmValue);
        Serial.printf("✅ 默认亮度已设为 %d%% (PWM=%d)\n", lastBrightness, pwmValue);
    }

    

    // ========== 2. 检查是否已配置 WiFi ==========
    if (!isWiFiConfigured()) {
        showAPInstructions();  // 抽取为独立函数更清晰
        startAPWebServer();

        while (true) {
            handleAPClient();
            checkFactoryReset();
            delay(10);
        }
    }

    // ========== 3. 已配置，连接 WiFi ==========
    String ssid, pass;
    if (!getSavedWiFiConfig(ssid, pass)) {
        tft.println("配置读取失败!");
        Serial.println("❌ 无法读取已保存的WiFi配置");
        delay(3000);
        ESP.restart();
    }

    tft.println("连接WiFi...");
    Serial.print("📶 连接WiFi: ");
    Serial.println(ssid);

    WiFi.begin(ssid.c_str(), pass.c_str());

    unsigned long connectTimeout = millis() + 30000;
    bool connected = false;

    while (millis() < connectTimeout && !connected) {
        if (WiFi.status() == WL_CONNECTED) {
            connected = true;
        } else {
            checkFactoryReset();
            delay(10);
            if (millis() % 500 < 10) {
                Serial.print(".");
            }
        }
    }

    if (connected) {
        Serial.print(F("✅ 连接成功！IP: "));
        Serial.println(WiFi.localIP().toString());
        strlcpy(g_ssid, ssid.c_str(), sizeof(g_ssid));
        tft.printf("IP:\n%s", WiFi.localIP().toString().c_str());
    } else {
        Serial.println("\n❌ WiFi连接超时或已被重置");
        tft.println("连接失败或已重置!");
        delay(1000);
        ESP.restart();
    }

    // ========== 4. 音频初始化 ==========
    Audio::audio_info_callback = my_audio_info;

    // ========== 5. 配置 I2S 音频输出 ==========
    if (!audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT)) {
        Serial.println("❌ I2S 配置失败！");
        tft.fillScreen(TFT_RED);
        tft.setTextColor(TFT_WHITE);
        tft.setCursor(0, 50);
        tft.println("I2S 配置失败！");
        tft.unloadFont(); // ✅ 死循环前清理资源（可选）
        while (1) delay(10);
    }

    //初始化读取保存的声音大小
    preferences.begin(PREF_VOICE_NAMESPACE, false);
    int initVolume = preferences.getInt(PREF_VOICE_KEYL,-1);
    preferences.end();
    if (initVolume >= 0 && initVolume <= 21) {
        Serial.print("🔊读取到存储初始声音：");
        Serial.println(initVolume);
        audio.setVolume(initVolume);
        volumeLevel=initVolume;
    }else{
        Serial.print("🔊未读取到存储初始声音默认值为:10\n");
        audio.setVolume(volumeLevel);
    }
    // ========== 6. 启动 Web 服务器 ==========
    server.on("/", HTTP_GET, handleRoot);
    server.on("/config", HTTP_GET, handleConfig);
    server.on("/play", HTTP_POST, handlePlay);
    server.on("/setVolume", HTTP_POST, handleSetVolume);
    server.on("/screen", HTTP_GET, handleScreenControl);
    server.on("/setBrightness", HTTP_POST, handleSetBrightness);
    server.on("/stopPlay", HTTP_GET, handleStop);  // 添加这行
    // 获取当前播放源地址
    server.on("/getJsonUrl", HTTP_GET, []() {
        server.send(200, "text/plain", json_url);
    });
    // 保存新的播放源地址
    server.on("/setJsonUrl", HTTP_POST,handleSaveJsonUrl);

    //请求自动关机
    server.on("/shutdownoff", HTTP_POST, handleShutdown);
    //取消关机
    server.on("/canceloff", HTTP_GET, handleCancel);

    //获取开机声音
    server.on("/getDefaultVolume", HTTP_GET, []() {
        preferences.begin(PREF_VOICE_NAMESPACE, false);
        int loadedVolume = preferences.getInt(PREF_VOICE_KEYL,-1);
        preferences.end();
        // 判断是否成功读取
        if (loadedVolume != -1) {
            volumeLevel = loadedVolume;  // 赋值给你的变量
        }
        server.send(200, "text/plain", String(volumeLevel));
    });
    // 保存开机声音
    server.on("/setDefaultVolume", HTTP_POST,handleSavePowerVoice);

    //获取开机亮度
    server.on("/getDefaultBrightness", HTTP_GET, []() {
        preferences.begin(PREF_BRIGHTNESS_NAMESPACE, false);
        int loadedBrightness = preferences.getInt(PREF_BRIGHTNESS_KEYL,0);
        preferences.end();
        server.send(200, "text/plain", String(lastBrightness));
    });
    // 保存开机亮度
    server.on("/setDefaultBrightness", HTTP_POST,handleSavePowerBrightness);
    


    server.begin();

    Serial.print(F("🌍 Web服务器已启动，访问: http://"));
    Serial.print(WiFi.localIP());
    Serial.println(F("/"));

    // ========== 7. 显示主界面信息 ==========
    updateScreen();  // 👉 注意：这里可能会用到中文！

    // 🔹【关键优化】📌 是否卸载字体？
    // ✅ 如果 updateScreen 或后续页面不需要中文 → 可在此卸载
    // ❌ 如果会显示“正在播放”、“音量”等中文 → 请不要卸载！

    #if defined(USE_CHINESE_IN_LOOP) || defined(ALWAYS_SHOW_CHINESE)
        // 保留字体
        Serial.println("ℹ️ 中文字体保留在内存中...");
    #else
        tft.unloadFont();  // ✅ 安全释放：仅当确认后面不会再使用中文
        Serial.println("🗑️ 中文字体已释放，节省 RAM");
    #endif

    // ========== 7. 读取上次保存的播放 URL ==========
    preferences.begin(PREF_URL_NAMESPACE , false); // 打开命名空间（false = 可读写）
    size_t len = preferences.getString(PREF_URL_KEY_URL, current_url, sizeof(current_url));
    preferences.end(); // 关闭命名空间
    if (len > 0) {
        Serial.printf("✅ 加载上次播放的 URL: %s\n", current_url);
    } else {
        Serial.printf("ℹ️ 未找到保存的 URL，使用默认: %s\n", current_url);
        // current_url 已有默认值，无需再赋值
    }

    // ========== 8. 加载播放源 JSON 地址 ==========
    preferences.begin(PREF_JSON_NAMESPACE, false);
    len = preferences.getString(PREF_JSON_KEY_URL, json_url, sizeof(json_url));
    preferences.end();
    if (len > 0) {
        Serial.printf("✅ 加载自定义播放源: %s\n", json_url);
    } else {
        Serial.printf("ℹ️ 使用默认播放源: %s\n", json_url);
    }

    // ========== 9. 开始播放 ==========
    playUrl(current_url);
}

// ==================== Loop ====================
void loop() {
     // 🔽 每次循环都检测是否长按按钮
    checkFactoryReset();
    //声音
    audio.loop();
    server.handleClient();

    // 检查是否到关机时间（非阻塞）
    handleShutdownNonBlocking();

    yield();

    // 每秒刷新一次屏幕
    if (millis() - lastScreenUpdate > 1000) {
        lastScreenUpdate = millis();
        updateScreen();
    }
}